/**
 * @Copyright：2013 杭州海康威视系统技术有限公司
 * @Address：http://www.hikvision.com
 * @Project GSS加油站出入口车牌及客户管理系统
 */
package com.hlkj.baojin.utils.hik;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.databinding.utils.BeanUtil;
import org.apache.axis2.engine.DefaultObjectSupplier;
import org.apache.axis2.rpc.client.RPCServiceClient;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.lang3.StringUtils;

import javax.xml.namespace.QName;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 基于OMElement实现的WebService客户端
 * 
 * @author xuweiyf1
 * @date 2013-6-18
 */
@SuppressWarnings("unchecked")
public final class WebServiceOmeClient {

	// 请求超时时间
	private static final int WS_TIMEOUT = 60000;

	// WebService服务地址
	private static String server = "122.227.217.10";

	// WebService端口号
	private static int port = 90;

	// WebService访问路径
	private final String path;

	// 命名空间
	private final String namespace;

	// 前缀
	private final String prefix;

	private static ThreadLocal<ServiceClient> serviceClients = new ThreadLocal<ServiceClient>();

	private static ThreadLocal<RPCServiceClient> rpcServiceClients = new ThreadLocal<RPCServiceClient>();

	/**
	 * 构造方法, 不指定IP、端口号，在调用方法是指定
	 * 
	 * @param:path WebService访问路径
	 * @param:namespace 命名空间
	 * @param:prefix 元素的前缀
	 */
	public WebServiceOmeClient(String path, String namespace, String prefix)
			throws Exception {
		this(server, port, path, namespace, prefix);
	}

	/**
	 * 构造方法, 指定IP、端口号，在调用方法时可不指定
	 * 
	 * @param:server 服务器IP地址
	 * @param:port 端口号
	 * @param:path WebService访问路径
	 * @param:namespace 命名空间
	 * @param:prefix 元素的前缀
	 * @throws Exception
	 */
	public WebServiceOmeClient(String server, int port, String path,
                               String namespace, String prefix) throws Exception {
		LogUtil.logDebug(this, "创建WebServiceOmeClient对象");

		/* 服务器IP地址为空时, 不允许创建 */
		if (isStringEmpty(server)) {
			LogUtil.logError(this, "无法创建对象，服务器IP地址不能为空");
			throw new Exception("无法创建对象，服务器IP地址不能为空");
		}

		/* 端口号不是正整数时, 不允许创建 */
		if (port <= 0) {
			LogUtil.logError(this, "无法创建对象，端口号必须是正整数");
			throw new Exception("无法创建对象，端口号必须是正整数");
		}

		/* WebService访问路径为空时, 不允许创建 */
		if (isStringEmpty(path)) {
			LogUtil.logError(this, "无法创建对象，WebService访问路径不能为空");
			throw new Exception("无法创建对象，WebService访问路径不能为空");
		}

		this.server = server;
		this.port = port;
		this.path = path;
		this.namespace = namespace;
		this.prefix = prefix;
	}

	/**
	 * 调用WebService的方法，调用者须根据生成规则封装参数，一般均封转在Map中，将key作为自定义的根元素名称
	 * <p>
	 * 参数为数组类型，遍历数组，以数组对象的类名作为元素名称
	 * </p>
	 * <p>
	 * 参数为Map类型，以key值作为元素名称
	 * </p>
	 * <p>
	 * 参数为普通java对象，遍历其属性，以属性名称作为元素名称
	 * </p>
	 * <p>
	 * 参数为基本数据类型，直接作为根元素文本处理
	 * </p>
	 * 
	 * @param <T>
	 *            泛型指定返回类型
	 * @param:returnClass 远程过程返回值类
	 * @param:methodName 远程过程名称
	 * @param:args WebService接口的参数
	 * @return 泛型对象
	 * @throws Exception
	 */
	public <T> T invokeWsMethod(Class<T> returnClass, String methodName,
			Object... args) throws Exception {
		/* 打印调用WebService方法的详细信息 */
		LogUtil.logInfo(this, "调用WebService方法：", methodName, "参数：server[",
				server, "]port[", port, "]returnClass[", returnClass,
				"]args[ ",
				ObjectUtils.isArrayNotEmpty(args) ? StringUtils.join(args, ',')
						: "", "]");

		/* 验证有效性 */
		if (isStringEmpty(server) || port <= 0 || isStringEmpty(methodName)) {
			LogUtil.logError(this, "传入了无效参数[", server, ", ", port, ", ",
					methodName, "]");
			throw new Exception("传入了无效参数[" + server + ", " + port + ", "
					+ methodName + "]");
		}

		if (ObjectUtils.isArrayNotEmpty(args)
				&& args[0] instanceof WsArgOrderMap) {
			// 获得客户端对象，serviceClient记录状态
			RPCServiceClient serviceClient = getRPCServiceClient(server, port);
			serviceClient.getOptions().setProperty(HTTPConstants.CHUNKED,
					"false"); // 设置http限制
			// 获得OM工厂对象，用于创建OMElement对象
			OMFactory fac = OMAbstractFactory.getOMFactory();

			// 命名空间
			OMNamespace omNs = fac.createOMNamespace(namespace, prefix);
			LogUtil.logDebug(this, "命名空间：", namespace, ", 前缀：", prefix);

			// 根元素，以方法名作为元素名称
			OMElement root = fac.createOMElement(methodName, omNs);

			/* 生成根元素下EMElement对象，生成规则见setOMElement方法注释 */
			if (!isArrayEmpty(args)) {
				for (Object arg : args) {
					this.setOMElement(fac, root, omNs, arg);
				}
			}

			// 远程过程返回结果
			OMElement respOme = null;
			T res = null;

			try {
				/* 判断是否传入返回类型，调用远程方法 */
				if (returnClass == null) {
					LogUtil.logDebug(this,
							"调用了无返回结果的WebService方法sendRobust \r\n 输入xml：\r\n",
							root.toString());

					// 无返回结果远程调用
					serviceClient.sendRobust(root);
				} else {
					LogUtil.logDebug(this,
							"调用了有返回结果的WebService方法sendReceive \r\n 输入xml：\r\n",
							root.toString());

					// 有返回结果远程调用
					respOme = serviceClient.sendReceive(root);
				}

				/* 处理返回结果 */
				if (respOme != null) {
					res = this.getResult(returnClass, respOme);
				}

			} catch (AxisFault e) {
				// log.error("调用有返回值的远程过程失败", e);
				throw new Exception("调用有返回值的远程过程失败", e);
				// return null;
			} finally {
				try {
					// 清理连接通道
					serviceClient.cleanupTransport();
				} catch (AxisFault ignore) {
					LogUtil.logError(this, "清理连接通道失败", ignore);
				}
			}

			return res;
		} else {
			if (args != null) {
				for (int i = 0; i < args.length; i++) {
					Object object = args[i];
					if (object != null) {
						if (object.getClass().isArray()) {
							Object[] arr = (Object[]) object;
							if (arr.length == 0) {
								if (arr.getClass().getComponentType() == String.class) {
									args[i] = new String[] { "" };
								} else {
									args[i] = null;
								}
							}
						}
					}
				}
			}
			return call(methodName, returnClass, args);
		}

	}

	/**
	 * 对远程过程的返回结果进行处理
	 * 
	 * @param <T>
	 *            泛型指定返回类型
	 * @param:returnClass 远程过程返回值类型
	 * @param:respOme WebService远程方法返回的结果
	 * @return 处理后的返回值
	 * @throws Exception
	 */
	private <T> T getResult(Class<T> returnClass, OMElement respOme)
			throws Exception {
		LogUtil.logInfo(this, "处理WebService方法返回的结果，转化为", returnClass, "类型");

		/* 验证有效性 */
		if (returnClass == null || respOme == null) {
			LogUtil.logError(this, "传入了无效参数[" + returnClass + ", " + respOme,
					"]");
			throw new Exception("传入了无效参数[" + returnClass + ", " + respOme + "]");
		}

		// 调用方法获得返回值
		Object[] response;

		try {
			/* 解析返回结果 */
			if (returnClass.isArray()) {
				// 返回结果为数组类型，
				response = BeanUtil.deserialize(respOme.getFirstElement(),
						new Class[] { returnClass },
						new DefaultObjectSupplier());
			} else {
				// 返回结果为非数组类型，直接解析
				response = BeanUtil.deserialize(respOme,
						new Class[] { returnClass },
						new DefaultObjectSupplier());
			}
		} catch (AxisFault e) {
			LogUtil.logError(this, "解析远程返回结果失败", e);
			return null;
		}

		/* 解析结果为空数组，直接返回 */
		if (isArrayEmpty(response)) {
			LogUtil.logError(this, "无法解析远程过程的返回结果!");
			return null;
		}

		// 获取指定泛型的返回值
		T result = (T) response[0];

		/* 无返回结果 */
		if (result == null) {
			LogUtil.logError(this, "无法解析远程过程的返回结果!");
		}

		return result;
	}

	/**
	 * 构建parent下的EMElement对象
	 * 
	 * @param:fac OMFactory对象，用于创建EMElement对象
	 * @param:parent 上级EMElement对象
	 * @param:omNs 命名空间
	 * @param:wsParam WebService参数对象
	 *                <p>
	 *                wsParam为数组类型，遍历数组递归该方法处理
	 *                </p>
	 *                <p>
	 *                wsParam为Map类型，以key值作为元素名称
	 *                </p>
	 *                <p>
	 *                wsParam为普通java对象，遍历其属性，以属性名称作为元素名称
	 *                </p>
	 *                <p>
	 *                wsParam为基本数据类型，直接作为根元素文本处理
	 *                </p>
	 * @throws Exception
	 */
	private void setOMElement(OMFactory fac, OMElement parent,
			OMNamespace omNs, Object wsParam) throws Exception {
		// 验证有效性
		validateWsParam(fac, parent, wsParam);

		/* 将普通java对象转化为OMElement对象 */
		Class pojoClass = wsParam.getClass();
		LogUtil.logDebug(this, "转化", parent.getLocalName(), "下的", pojoClass,
				"对象");

		if (pojoClass.isArray()) {
			// 处理数组
			processArrayParam(fac, parent, omNs, wsParam);
		} else if (wsParam instanceof WsArgOrderMap) {
			// 处理Map
			processMapParam(fac, parent, omNs, wsParam);
		} else if (!WsSimpleTypeMapper.isWsSimpleType(wsParam)) {
			// 处理非简单数据对象
			processClassParam(fac, parent, omNs, wsParam);
		} else {
			// 如果参数为基本数据类型直接加入上级元素
			parent.setText(String.valueOf(wsParam));
		}
	}

	/**
	 * 基于上层的OMElement对象，转化数组为子元素数组
	 * 
	 * @param:fac OMFactory对象，用于创建OMElement对象
	 * @param:parent 上级EMElement对象
	 * @param:omNs 命名空间
	 * @param:wsParam WebService参数对象
	 * @throws Exception
	 */
	private void processArrayParam(OMFactory fac, OMElement parent,
			OMNamespace omNs, Object wsParam) throws Exception {
		// 验证有效性
		validateWsParam(fac, parent, wsParam);

		/* 如果是数组，遍历每一个数组元素进行转化，添加到根元素（递归） */
		Object[] valueArr = (Object[]) wsParam;
		if (!isArrayEmpty(valueArr)) {
			String simpleName = valueArr.getClass().getComponentType()
					.getSimpleName();
			for (Object obj : valueArr) {
				// 用类名创建元素
				OMElement aOme = fac.createOMElement(firstToLower(simpleName),
						omNs);

				/* 以aOme为根元素，递归处理该对象作为子元素 */
				if (aOme != null) {
					if (!WsSimpleTypeMapper.isWsSimpleType(obj)) {
						// 如果值不是java基本类型，递归处理
						setOMElement(fac, aOme, omNs, obj);
					} else {
						LogUtil.logDebug(this, "生成OMElement对象: ",
								aOme.getLocalName(), ": ", String.valueOf(obj));

						/* 如果值是java基本类型，生成OMElement对象 */
						aOme.setText(String.valueOf(obj));
					}

					// 加入根元素
					parent.addChild(aOme);
				}
			}
		}
	}

	/**
	 * 基于上层的OMElement对象，转化Map对象为子元素
	 * 
	 * @param:fac OMFactory对象，用于创建OMElement对象
	 * @param:parent 上级EMElement对象
	 * @param:omNs 命名空间
	 * @param:wsParam WebService参数对象
	 * @throws Exception
	 */
	private void processMapParam(OMFactory fac, OMElement parent,
			OMNamespace omNs, Object wsParam) throws Exception {
		// 验证有效性
		validateWsParam(fac, parent, wsParam);

		/* 将Map转化为OMElement对象 */
		WsArgOrderMap valueMap = (WsArgOrderMap) wsParam;
		if (valueMap != null) {
			List<WsArgOrderMap.OEntry> entryList = valueMap.entryList();
			if (entryList == null || entryList.size() == 0) {
				return;
			}

			/* 遍历Map，对于每一个key生成一个元素 */
			for (WsArgOrderMap.OEntry entry : entryList) {
				if (entry == null) {
					continue;
				}

				// Entry对象的值
				Object value = entry.getValue();

				// Entry对象的key
				String key = entry.getKey().toString();

				if (!isStringEmpty(key) && value != null) {
					// 用key创建元素
					OMElement aOme = fac.createOMElement(key, omNs);

					/* 以aOme为根元素，递归处理该对象作为子元素 */
					if (aOme != null) {
						if (!WsSimpleTypeMapper.isWsSimpleType(value)) {
							// 如果值不是java基本类型，递归处理
							setOMElement(fac, aOme, omNs, value);
						} else {
							LogUtil.logDebug(this, "生成OMElement对象: ",
									aOme.getLocalName(), ": ",
									String.valueOf(value));

							/* 如果值是java基本类型，生成OMElement对象 */
							aOme.setText(String.valueOf(value));
						}

						// 加入根元素
						parent.addChild(aOme);
					}
				}
			}
		}
	}

	/**
	 * 基于上层的OMElement对象，转化复合数据类型为子元素
	 * 
	 * @param:fac OMFactory对象，用于创建OMElement对象
	 * @param:parent 上级EMElement对象
	 * @param:omNs 命名空间
	 * @param:wsParam WebService参数对象
	 * @throws Exception
	 */
	private void processClassParam(OMFactory fac, OMElement parent,
			OMNamespace omNs, Object wsParam) throws Exception {
		// 验证有效性
		validateWsParam(fac, parent, wsParam);

		/* 处理非java基本类型的pojo对象 */
		Field[] fields = wsParam.getClass().getDeclaredFields();
		if (!isArrayEmpty(fields)) {
			/* 遍历所有属性，用属性名创建元素 */
			for (Field field : fields) {
				if (field != null) {
					// 用属性名创建元素
					OMElement aOme = fac.createOMElement(field.getName(), omNs);

					if (aOme != null) {
						Object value = null;
						try {
							// 让属性值可访问
							field.setAccessible(true);

							/* 获得属性值 */
							value = field.get(wsParam);
							if (value == null) {
								parent.addChild(aOme);
								continue;
							}
						} catch (Exception e) {
							LogUtil.logError(this, "无法读取", field.getName(),
									"的属性值", e);
							throw new Exception("无法读取" + field.getName()
									+ "的属性值", e);
						}

						if (!WsSimpleTypeMapper.isWsSimpleType(value)) {
							// 如果值不是java基本类型，递归处理
							setOMElement(fac, aOme, omNs, value);
						} else {
							LogUtil.logDebug(this, "生成OMElement对象: ",
									aOme.getLocalName(), ": ",
									String.valueOf(value));

							// 如果值是java基本类型，生成OMElement对象
							aOme.setText(String.valueOf(value));
						}

						// 加入根元素
						parent.addChild(aOme);
					}
				}
			}
		}
	}

	/**
	 * 获取访问WebService的客户端对象
	 * 
	 * @param:path WebService访问路径
	 * @return 客户端对象
	 * @throws Exception
	 */
	private RPCServiceClient getRPCServiceClient(String server, int port)
			throws Exception {
		LogUtil.logDebug(this, "获取访问WebService的客户端对象：server=" + server
				+ "port=" + port);

		/* 验证有效性 */
		if (isStringEmpty(server) || port <= 0) {
			LogUtil.logError(this, "无法创建ServiceClient对象，传入的服务地址或端口号无效");
			throw new Exception("无法创建ServiceClient对象，传入的服务地址或端口号无效");
		}

		// 创建RPC客户端
		RPCServiceClient serviceClient = rpcServiceClients.get();

		if (serviceClient == null) {
			serviceClient = new RPCServiceClient();
			rpcServiceClients.set(serviceClient);
		}

		/* 设置客户端请求选项 */
		Options options = serviceClient.getOptions();

		// 请求超时时间
		options.setTimeOutInMilliSeconds(WS_TIMEOUT);

		// SOAP的版本
		options.setSoapVersionURI(org.apache.axiom.soap.SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI);

		// 调用WebService的URL
		StringBuilder urlSb = new StringBuilder(256);
		urlSb.append("http://").append(server).append(":").append(port)
				.append("/").append(path);

		// 根据URL创建EndpointReference对象
		options.setTo(new EndpointReference(urlSb.toString()));

		return serviceClient;
	}

	/**
	 * 获取访问WebService的客户端对象
	 * 
	 * @param:path WebService访问路径
	 * @return 客户端对象
	 * @throws Exception
	 */
	private ServiceClient getServiceClient(String server, int port)
			throws Exception {
		LogUtil.logDebug(this, "获取访问WebService的客户端对象：server=" + server
				+ "port=" + port);

		/* 验证有效性 */
		if (isStringEmpty(server) || port <= 0) {
			LogUtil.logError(this, "无法创建ServiceClient对象，传入的服务地址或端口号无效");
			throw new Exception("无法创建ServiceClient对象，传入的服务地址或端口号无效");
		}

		// 创建RPC客户端
		ServiceClient serviceClient = serviceClients.get();

		if (serviceClient == null) {
			serviceClient = new ServiceClient();
			serviceClients.set(serviceClient);
		}

		/* 设置客户端请求选项 */
		Options options = serviceClient.getOptions();

		// 请求超时时间
		options.setTimeOutInMilliSeconds(WS_TIMEOUT);

		// SOAP的版本
		options.setSoapVersionURI(org.apache.axiom.soap.SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI);

		// 调用WebService的URL
		StringBuilder urlSb = new StringBuilder(256);
		urlSb.append("http://").append(server).append(":").append(port)
				.append("/").append(path);

		// 根据URL创建EndpointReference对象
		options.setTo(new EndpointReference(urlSb.toString()));

		return serviceClient;
	}

	/**
	 * 验证想要转化的对象
	 * 
	 * @param:wsParam 想要转化的对象
	 * @throws Exception
	 */
	private void validateWsParam(OMFactory fac, OMElement parent, Object wsParam)
			throws Exception {
		if (fac == null) {
			LogUtil.logError(this, "尚未初始化OMFactory对象");
			throw new Exception("尚未初始化OMFactory对象");
		}

		if (parent == null) {
			LogUtil.logError(this, "上级OMElement对象不能为空");
			throw new Exception("上级OMElement对象不能为空");
		}

		if (wsParam == null) {
			LogUtil.logError(this, "想要转化的对象不能为空");
			throw new Exception("想要转化的对象不能为空");
		}
	}

	/**
	 * 字符串是否为空
	 * 
	 * @author majianbo Mar 11, 2011 12:28:03 PM
	 * @param string
	 * @return true 空 false 不空
	 */
	public static boolean isStringEmpty(String string) {
		return ((string == null) || string.isEmpty()) ? true : false;
	}

	/**
	 * 数组是否为空
	 * 
	 * @author majianbo Mar 11, 2011 10:37:16 AM
	 * @param array
	 * @return true 空 false 不空
	 */
	public static boolean isArrayEmpty(Object[] array) {
		return (array == null || array.length == 0) ? true : false;
	}

	/**
	 * Map是否为空
	 * 
	 * @author majianbo Mar 11, 2011 10:37:20 AM
	 * @param map
	 * @return true 空 false 不空
	 */
	public static boolean isMapEmpty(Map<?, ?> map) {
		return (map == null || map.isEmpty()) ? true : false;
	}

	/**
	 * 把第一个字符变成小写
	 * 
	 * @param str
	 *            字符串
	 * @return 转化后的字符串
	 */
	public static String firstToLower(String str) {
		if (str == null || str.isEmpty()) {
			return "";
		}
		return str.substring(0, 1).toLowerCase()
				+ str.substring(1, str.length());
	}

	public <T> T call(String method, Class<T> returnType, Object... params)
			throws Exception {
		LogUtil.logInfo(this, "调用webservice,method=" + method + ",returnType="
				+ returnType + ",params=" + Arrays.toString(params));
		if (params != null) {
			for (int i = 0; i < params.length; i++) {
				Object object = params[i];
				if (object != null) {
					if (object.getClass().isArray()) {
						Object[] arr = (Object[]) object;
						if (arr.length == 0) {
							if (arr.getClass().getComponentType() == String.class) {
								params[i] = new String[] { "" };
							} else {
								params[i] = null;
							}
						}
					}
				}
			}
		}
		RPCServiceClient client = rpcServiceClients.get();
		try {
			if (client == null) {
				client = new RPCServiceClient();
				rpcServiceClients.set(client);
			}

			Options option = client.getOptions();
			// 指定客户端访问的webservice服务器端地址
			EndpointReference erf = new EndpointReference(baseUrl());
			option.setAction(method);
			option.setTo(erf);
			option.setTimeOutInMilliSeconds(6000);
			// 指定命名空间,指定要调用的方法
			QName name = new QName(namespace, method);
			if (returnType == null) {// 无参数返回的webservice接口调用
				client.invokeRobust(name, params);
				return null;
			} else {
				// 创建返回的参数类型
				Class[] returnTypes = new Class[] { returnType };
				// 调用远程服务，得到返回的object数组
				Object[] response = client.invokeBlocking(name, params,
						returnTypes);
				return (T) response[0];
			}
		} catch (AxisFault e) {
			throw new Exception("调用webservice出错", e);
		} finally {
			try {
				if (client != null) {
					client.cleanupTransport();
				}
			} catch (AxisFault e) {
				LogUtil.logError(this, "清理webservice出错" + e);
			}
		}

	}

	public <T> T call(Class<T> returnType, String method, Object... params)
			throws Exception {
		return call(method, returnType, params);
	}

	private String baseUrl() {
		// String flag =
		// String.valueOf(ServletActionContext.getRequest().getSession().getAttribute("flag"));
		// if("0".equals(flag)){
		// return "http://" + server + ":" + port + "/" + path;
		// }else{
		// return "https://" + server + ":" + port + "/" + path;
		// }
		// }
//		return CfgMgr.getProtocol() + "://" + server + ":" + port + "/" + path;
		return "http://" + server + ":" + port + "/" + path;
	}
}
